package border;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;

/**
 * Abstract class that is used for making resizable selection box.  See <code>RectilinearSelectionComponent</code>
 * for an example of how it's used
 * 
 * For an explanation of this implementation, see :
 * http://zetcode.com/tutorials/javaswingtutorial/resizablecomponent/
 * 
 * @author cshanes
 *
 */
public abstract class ResizableSelectionComponent extends JComponent {
    
    private static final long serialVersionUID = -9082852018830826082L;

    private static final int MIN_HEIGHT = 5;
    
    //panel used for "background" of this resizable component
    private JPanel panel = new JPanel(null);
    
    int maxWidth;
    int maxHeight;

    public ResizableSelectionComponent(int[] locations, int[] cursors, int maxWidth, int maxHeight, Rectangle startingBounds) {
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
        
        setLayout(new BorderLayout());
        panel.setBackground(new Color(0, 0, 0, 0));
        add(panel);
        setBorder(new ResizableBorder(locations, cursors));
        addMouseListener(resizeListener);
        addMouseMotionListener(resizeListener);
        this.setBounds(startingBounds);
    }

    private void resize() {
        if (getParent() != null) {
            ((JComponent) getParent()).revalidate();
        }
    }

    MouseInputListener resizeListener = new MouseInputAdapter() {
        public void mouseMoved(MouseEvent me) {
            if (hasFocus()) {
                ResizableBorder border = (ResizableBorder) getBorder();
                setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
            }
        }

        public void mouseExited(MouseEvent mouseEvent) {
            setCursor(Cursor.getDefaultCursor());
        }

        private int cursor;
        private Point startPos = null;

        public void mousePressed(MouseEvent me) {
            ResizableBorder border = (ResizableBorder) getBorder();
            cursor = border.getCursor(me);
            startPos = me.getPoint();
            requestFocus();
            repaint();
        }

        public void mouseDragged(MouseEvent me) {

            if (startPos != null) {

                int x = getX();
                int y = getY();
                int w = getWidth();
                int h = getHeight();

                int dx = me.getX() - startPos.x;
                int dy = me.getY() - startPos.y;

                switch (cursor) {
                case Cursor.N_RESIZE_CURSOR:
                    if (!(h - dy < MIN_HEIGHT)) {
                        if (hitNorthBoundary(y, dy)) {
                            dy = (0 - y);
                            setBounds(x, 0, w, h - dy);
                        } else {
                            setBounds(x, y + dy, w, h - dy);
                        }
                        resize();

                        yValueChanged(y + dy);
                        heightValueChanged(h - dy);
                    }
                    break;

                case Cursor.S_RESIZE_CURSOR:
                    if (!(h + dy < MIN_HEIGHT)) {
                        if (hitSouthBoundary(y, dy, h)) {
                            dy = maxHeight - h - y;
                            setBounds(x, y, w, maxHeight - y);
                        } else {
                            setBounds(x, y, w, h + dy);
                        }
                        startPos = me.getPoint();
                        resize();

                        heightValueChanged(h + dy);
                    }
                    break;

                case Cursor.W_RESIZE_CURSOR:
                    if (!(w - dx < 50)) {
                        if (hitWestBoundary(x, dx)) {
                            dx = (0 - x);
                            setBounds(0, y, w - dx, h);
                        } else {
                            setBounds(x + dx, y, w - dx, h);
                        }
                        resize();

                        xValueChanged(x + dx);
                        widthValueChanged(w - dx);
                    }
                    break;

                case Cursor.E_RESIZE_CURSOR:
                    if (!(w + dx < 50)) {
                        if (hitEastBoundary(x, dx, w)) {
                            dx = maxWidth - w - x;
                            setBounds(x, y, maxWidth - x, h);
                        } else {
                            setBounds(x, y, w + dx, h);
                        }
                        startPos = me.getPoint();
                        resize();

                        widthValueChanged(w + dx);
                    }
                    break;

                case Cursor.NW_RESIZE_CURSOR:
                    if (!(w - dx < 50) && !(h - dy < MIN_HEIGHT)) {
                        if (hitWestBoundary(x, dx)) {
                            dx = (0 - x);
                            setBounds(0, y, w - dx, h);
                        }
                        if (hitNorthBoundary(y, dy)) {
                            dy = (0 - y);
                            setBounds(x, 0, w, h - dy);
                        }

                        setBounds(x + dx, y + dy, w - dx, h - dy);
                        resize();

                        xValueChanged(x + dx);
                        yValueChanged(y + dy);
                        widthValueChanged(w - dx);
                        heightValueChanged(h - dy);
                    }
                    break;

                case Cursor.NE_RESIZE_CURSOR:
                    if (!(w + dx < 50) && !(h - dy < MIN_HEIGHT)) {
                        if (hitEastBoundary(x, dx, w)) {
                            dx = maxWidth - w - x;
                            setBounds(x, y, maxWidth - x, h);
                        }
                        if (hitNorthBoundary(y, dy)) {
                            dy = (0 - y);
                            setBounds(x, 0, w, h - dy);
                        }
                        setBounds(x, y + dy, w + dx, h - dy);
                        startPos = new Point(me.getX(), startPos.y);
                        resize();

                        yValueChanged(y + dy);
                        widthValueChanged(w + dx);
                        heightValueChanged(h - dy);
                    }
                    break;

                case Cursor.SW_RESIZE_CURSOR:
                    if (!(w - dx < 50) && !(h + dy < MIN_HEIGHT)) {
                        if (hitWestBoundary(x, dx)) {
                            dx = (0 - x);
                            setBounds(0, y, w - dx, h);
                        }
                        if (hitSouthBoundary(y, dy, h)) {
                            dy = maxHeight - h - y;
                            setBounds(x, y, w, maxHeight - y);
                        }
                        setBounds(x + dx, y, w - dx, h + dy);
                        startPos = new Point(startPos.x, me.getY());
                        resize();

                        xValueChanged(x + dx);
                        widthValueChanged(w - dx);
                        heightValueChanged(h + dy);
                    }
                    break;

                case Cursor.SE_RESIZE_CURSOR:
                    if (!(w + dx < 50) && !(h + dy < MIN_HEIGHT)) {
                        if (hitEastBoundary(x, dx, w)) {
                            dx = maxWidth - w - x;
                            setBounds(x, y, maxWidth - x, h);
                        }
                        if (hitSouthBoundary(y, dy, h)) {
                            dy = maxHeight - h - y;
                            setBounds(x, y, w, maxHeight - y);
                        }
                        setBounds(x, y, w + dx, h + dy);
                        startPos = me.getPoint();
                        resize();

                        widthValueChanged(w + dx);
                        heightValueChanged(h + dy);
                    }
                    break;

                case Cursor.MOVE_CURSOR:
                    // TODO: don't let user move the selection out of image area
                    Rectangle bounds = getBounds();

                    if (x + dx < 0)
                        dx = (0 - x);
                    else if (x + dx + w > maxWidth)
                        dx = (maxWidth - (x + w));

                    if (y + dy < 0)
                        dy = (0 - y);
                    else if (y + dy + h > maxHeight)
                        dy = (maxHeight - (y + h));

                    
                    if (!allowXTranslation()) {
                        dx = 0;
                    }
                    if (!allowYTranslation()) {
                        dy = 0;
                    }
                    
                    bounds.translate(dx, dy);
                    setBounds(bounds);
                    resize();

                    xValueChanged(x + dx);
                    yValueChanged(y + dy);
                }

                setCursor(Cursor.getPredefinedCursor(cursor));
            }
        }

        public void mouseReleased(MouseEvent mouseEvent) {
            startPos = null;
        }
    };
    
    /**
     * Override method used so extending classes can specify whether to allow X translation
     * 
     * @return
     */
    protected boolean allowXTranslation() {
        return true;
    }
    
    /**
     * Override method used so extending classes can specify whether to allow Y translation
     * 
     * @return
     */
    protected boolean allowYTranslation() {
        return true;
    }
    
    private boolean hitWestBoundary(int x, int dx) {
        return (x + dx < 0);
    }

    private boolean hitNorthBoundary(int y, int dy) {
        return (y + dy < 0);
    }

    private boolean hitEastBoundary(int x, int dx, int w) {
        return (w + x + dx > maxWidth);
    }

    private boolean hitSouthBoundary(int y, int dy, int h) {
        return (h + y + dy > maxHeight);
    }
    
    public void setMaxWidth(int width) {
        maxWidth = width;
    }
    
    public void setMaxHeight(int height) {
        maxHeight = height;
    }
    
    protected abstract void xValueChanged(Integer newX);

    protected abstract void yValueChanged(Integer newY);

    protected abstract void widthValueChanged(Integer newWidth);

    protected abstract void heightValueChanged(Integer newHeight);
    
}
